<!DOCTYPE html>
<html lang="zh-CN">

<!-- Head tag -->
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  
  <meta name="baidu-site-verification" content="undefined" />
  <meta name="google-site-verification" content="undefined" />
  <meta name="msvalidate.01" content="undefined" />
  <meta name="360-site-verification" content="undefined" />
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <!-- if page has tags, then add tags to keyword -->
  
  
  <meta name="keywords" content=",Golang汇编,Golang底层,Golang,">
  <!-- page.description has higher priority -->
  <meta name="description" content="Golang汇编第二篇文章，从汇编和栈的角度分析函数调用">
  <link rel="shortcut icon" href="/blog/img/logo.webp">
  <title>
    
    Golang汇编(二)-函数调用 | Evrn
    
  </title>

  <link rel="canonical" href="/blog/post/go-asm-method.html">
  
<link rel="stylesheet" href="/blog/css/reset.css">

  
<link rel="stylesheet" href="/blog/css/layout.css">

  
<link rel="stylesheet" href="/blog/css/markdown.css">

  <!-- Pygments Highlight CSS -->
  
<link rel="stylesheet" href="/blog/css/highlight.css">

  
<link rel="stylesheet" href="/blog/css/geektutu.css">

  <!-- global function -->
  <script>
    window.globalAddScript = function (url, onload, onerror) {
      var s = document.createElement('script');
      s.src = url;
      onload && (s.onload = onload);
      onerror && (s.onerror = onerror);
      document.body.appendChild(s);
    }
    window.globalAddCss = function (url) {
      var s = document.createElement('link');
      s.rel = 'stylesheet';
      s.href = url;
      document.body.appendChild(s);
    }
    window.getPosition = function (ele) {
      var x = 0, y = 0;
      while (ele) {
        x += (ele.offsetLeft - ele.scrollLeft + ele.clientLeft);
        y += (ele.offsetTop - ele.scrollTop + ele.clientTop);
        ele = ele.offsetParent;
      }
      return { x: x, y: y };
    }
    window.getDom = function (str) { return document.querySelector(str) }
  </script>
  <!-- google ad -->
  
<meta name="generator" content="Hexo 4.2.0"></head>

<body>
    <header class="gkt-header col-xs-12 padding-0">
    <div id="gkt-nav" class="gkt-header-container">
        <a href="/blog/" class="gkt-header-title float-left">
            <img class="float-left" src="/blog/img/logo.webp" alt="">
            <span>Evrn</span>
        </a>
        <nav class="gkt-header-nav text-right">
            <ul>
                <li><a class="hidden-xs" href="/blog/">主页</a></li>
                <li><a href="/blog/series/">专题</a></li>
                <li><a href="/blog/archives/">归档</a></li>
                <li><a href="/blog/post/link.html">友链</a></li>
                <li><a href="/blog/post/about.html">留言</a></li>
            </ul>
        </nav>
    </div>
    <div id="gkt-cate-nav" class="gkt-header-container hidden-xs">
        
        <nav class="gkt-header-nav float-left">
            <ul>
                
                
                <li class="gkt-cate-name float-left ">
                    <a class="float-left" href="/blog/post/go-learn-interface.html">Golang学习</a>
                    
                </li>
                
                <li class="gkt-cate-name float-left active">
                    <a class="float-left" href="/blog/post/go-asm-indroduce.html">Golang汇编</a>
                    
                </li>
                
                <li class="gkt-cate-name float-left ">
                    <a class="float-left" href="/blog/post/tcp-three-way-handshake.html">基础</a>
                    
                </li>
                
                <li class="gkt-cate-name float-left ">
                    <a class="float-left" href="/blog/post/go-how-to-build.html">Golang底层</a>
                    
                </li>
                
                <li class="gkt-cate-name float-left ">
                    <a class="float-left" href="/blog/post/mysql-index-use.html">MySQL</a>
                    
                </li>
                
            </ul>
        </nav>
    </div>
</header>
<div style="height: 44px"></div>
<div class="hidden-xs" style="height: 44px"></div>
<script>
    (function () {
        window.addEventListener('scroll', function () {
            if (window.innerWidth < 768) {
                return;
            }
            var nav = document.querySelector('#gkt-nav');
            var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
            scrollTop > 50 && (nav.classList.add('hide'));
            scrollTop <= 50 && (nav.classList.remove('hide'));
        });
        var cateNavs = document.querySelectorAll('#gkt-cate-nav>nav>ul>li');
        [].slice.call(cateNavs).forEach(function (item) {
            var sub = item.querySelector('.gkt-sub-cate');
            if (!sub) return;
            item.addEventListener('mouseenter', function (e) { sub.style.display = 'block'; }, false);
            item.addEventListener('mouseleave', function (e) { sub.style.display = 'none'; }, false);
        })
    })();
</script>
    <!-- Main Content -->
    <div class="main-container">
        <!-- Main Content -->
<main class="col-xs-12 padding-0 markdown-it">
    <!-- Post Container -->
    
    
    <!-- Post Content -->
<div class="float-left post-container box-shadow">
    <div class="u-arrow-wrapper hidden-xs">
        
        <a class="float-left" href="/blog/post/go-asm-indroduce.html"><i class="u-arrow-left"></i></a>
        
        
        <a class="float-right" href="/blog/post/go-asm-my-asm.html"><i class="u-arrow-right"></i></a>
        
    </div>
    <article class="col-xs-12">
        <h1> Golang汇编(二)-函数调用 </h1>

        

        
        <div class="hidden-lg hidden-md series_links">
            <p> <strong> Golang汇编系列文章链接：</strong></p>
            <ul>
                
                <li>
                    <a href="/blog/post/go-asm-indroduce.html">Golang汇编(一)-Plan9介绍及指令详解</a>
                    <span class="post-item-date">(Feb 6, 2020)</span>
                </li>
                
                <li>
                    <a href="/blog/post/go-asm-method.html">Golang汇编(二)-函数调用</a>
                    <span class="post-item-date">(Feb 16, 2020)</span>
                </li>
                
                <li>
                    <a href="/blog/post/go-asm-my-asm.html">Golang汇编(三)-编写自己的汇编代码</a>
                    <span class="post-item-date">(Feb 19, 2020)</span>
                </li>
                
                <li>
                    <a href="/blog/post/go-asm-datastruct.html">Golang汇编(四)-汇编眼中的数据结构</a>
                    <span class="post-item-date">(Feb 19, 2020)</span>
                </li>
                
            </ul>
        </div>
        
        
        
        <h2 id="函数调用栈"><a href="#函数调用栈" class="headerlink" title="函数调用栈"></a>函数调用栈</h2><p><a href="https://www.zhihu.com/question/36103513" target="_blank" rel="noopener">堆、栈的地址高低？ 栈的增长方向？</a></p>
<p>首先介绍一下栈</p>
<p>一般操作系统的栈会选择<strong>向下增长</strong>，32位Linux/x86进程地址空间模型如下</p>
<p><img src="http://river.marvelthanos.com/blog/20200302/zy62kbJoYbGn.png?imageslim" alt="mark"></p>
<p>用户栈的大小是固定的，Linux中默认为8192KB，运行时内存占用超过上限，程序会崩溃掉并报告segment错误。</p>
<h3 id="函数调用栈-1"><a href="#函数调用栈-1" class="headerlink" title="函数调用栈"></a>函数调用栈</h3><p><img src="http://river.marvelthanos.com/blog/20200302/73s12Exjgq1s.png?imageslim" alt="mark"></p>
<p>从图中可以看出</p>
<p>输入参数和返回值属于<strong>调用者</strong>（caller）的栈帧</p>
<p>只有局部变量和子调用(callee)的参数和返回值才是属于被调用者(<strong>当前函数</strong>)的栈帧</p>
<h3 id="Golang实际的调用栈"><a href="#Golang实际的调用栈" class="headerlink" title="Golang实际的调用栈"></a>Golang实际的调用栈</h3><blockquote>
<p>从这里开始所有图解和代码都基于GOOS=linux GOARCH=amd64</p>
</blockquote>
<p><a href="https://github.com/cch123/golang-notes/blob/master/assembly.md#plan9-assembly-完全解析" target="_blank" rel="noopener">plan9-assembly-完全解析</a></p>
<p><img src="http://river.marvelthanos.com/blog/20200302/jPj06R6RE7AO.png?imageslim" alt="mark"></p>
<p>解释一下上面的名词</p>
<ul>
<li><p><strong>伪FP</strong>: plan9引入用来访问参数和局部变量的特殊寄存器，使用形如 <code>symbol+offset(FP)</code> 的方式，引用函数的输入参数，<code>symbol</code>为了助记相当于参数名</p>
<p>例如 </p>
<p><code>arg0+0(FP)</code>表示对FP寄存器指向地址偏移0字节取值，</p>
<p><code>arg1+8(FP)</code>表示对FP寄存器指向地址偏移增加8字节再取值</p>
<p>使用 FP 不加 symbol 时，无法通过编译，在汇编层面来讲，symbol 并没有什么用，加 symbol 主要是为了提升代码可读性。</p>
<p>也<strong>可以看成是调用者caller 的SP</strong></p>
</li>
<li><p><strong>伪SP</strong>: <strong>实际上是当前栈帧的 BP 位置</strong>,plan9 的这个 SP 寄存器指向当前栈帧的局部变量的开始位置，使用形如 <code>symbol+offset(SP)</code> 的方式，引用函数的局部变量。offset 的合法取值是 [-framesize, 0)，注意是个左闭右开的区间。假如局部变量都是 8 字节，那么第一个局部变量就可以用 <code>localvar0-8(SP)</code> 来表示。在栈帧 size 为 0 的情况下，伪寄存器 SP 和硬件寄存器 SP 指向同一位置。手写汇编代码时，如果是 <code>symbol+offset(SP)</code> 形式，则表示伪寄存器 SP。如果是 <code>offset(SP)</code> 则表示硬件寄存器 SP。</p>
</li>
<li><p><strong>硬件SP</strong>：永远指向栈顶<strong>栈指针</strong></p>
<p>(0)SP 可以看作是<strong>调用者caller 的PC</strong>指针</p>
</li>
<li><p><strong>callee</strong>：函数内调用的子函数</p>
</li>
<li><p><strong>BP</strong>:指向栈帧栈底的指针</p>
</li>
<li><p><strong>return addr</strong>：每次调用Call时由编译器自动插入</p>
</li>
<li><p><strong>caller BP</strong>：调用这个函数的调用者的BP地址</p>
</li>
</ul>
<p>  进一步说明：</p>
<ol>
<li>函数栈帧由伪SP(BP,指向栈底<strong>帧指针</strong>) 硬件SP(指向栈顶<strong>栈指针</strong>)共同界定，大小就是framesize，当framesize分配大于实际的大小的时候，局部变量和子调用参数和返回值分列栈帧两端，中间会空出<strong>未使用部分</strong>，引用的时候要注意</li>
<li>在 go tool objdump/go tool compile -S 输出的代码中，是<strong>没有伪 SP 和 FP 寄存器的</strong>，我们上面说的区分伪 SP 和硬件 SP 寄存器的方法，对于上述两个命令的输出结果是没法使用的。在编译和反汇编的结果中，<strong>只有真实的 SP 寄存器。</strong></li>
<li>伪 SP 和 FP 寄存器主要用于手写汇编的时候引用参数局部变量。</li>
<li>FP 和 Go 的官方源代码里的 framepointer 不是一回事，源代码里的 framepointer 指的是 caller BP 寄存器的值，在这里和 caller 的伪 SP 是值是相等的。</li>
</ol>
<h2 id="简单案例"><a href="#简单案例" class="headerlink" title="简单案例"></a>简单案例</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//asm1.go </span></span><br><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">add</span><span class="params">(a, b <span class="keyword">int</span>)</span> <span class="params">(<span class="keyword">int</span>, <span class="keyword">bool</span>)</span></span> &#123;</span><br><span class="line">	c := <span class="number">3</span></span><br><span class="line">	d := a + b + c</span><br><span class="line">	<span class="keyword">return</span> d, <span class="literal">true</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">	e,u:=add(<span class="number">10</span>, <span class="number">32</span>)</span><br><span class="line">	<span class="keyword">if</span> u&#123;</span><br><span class="line">		e+=<span class="number">1</span></span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>通过</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">go tool compile -N -l -S asm1.go</span><br></pre></td></tr></table></figure>

<p>得到golang的中间汇编代码，去除无关代码（如FUNCDATA，PCDATA等GC相关）后,</p>
<p>注意这里的SP都是<strong>硬件SP</strong></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line">&quot;&quot;.add STEXT nosplit size&#x3D;74 args&#x3D;0x20 locals&#x3D;0x18</span><br><span class="line">00000 (asm1.go:3)	TEXT	&quot;&quot;.add(SB), NOSPLIT, $24-32</span><br><span class="line">00000 (asm1.go:3)	SUBQ	$24, SP    &#x2F;&#x2F;对SP做减法，为函数分配函数栈帧</span><br><span class="line">00004 (asm1.go:3)	MOVQ	BP, 16(SP) &#x2F;&#x2F;将当前BP压栈暂存</span><br><span class="line">00009 (asm1.go:3)	LEAQ	16(SP), BP &#x2F;&#x2F;将当前sp的地址作为新的BP</span><br><span class="line">00014 (asm1.go:3)	MOVQ	$0, &quot;&quot;.~r2+48(SP) &#x2F;&#x2F;初始化返回值1 int</span><br><span class="line">00023 (asm1.go:3)	MOVB	$0, &quot;&quot;.~r3+56(SP) &#x2F;&#x2F;初始化返回值2 bool</span><br><span class="line">00028 (asm1.go:4)	MOVQ	$3, &quot;&quot;.c+8(SP) 	&#x2F;&#x2F;设置局部变量c为3</span><br><span class="line">00037 (asm1.go:5)	MOVQ	&quot;&quot;.a+32(SP), AX &#x2F;&#x2F;获取参数a</span><br><span class="line">00042 (asm1.go:5)	ADDQ	&quot;&quot;.b+40(SP), AX &#x2F;&#x2F;获取参数b并与a相加</span><br><span class="line">00047 (asm1.go:5)	ADDQ	$3, AX			&#x2F;&#x2F;编译器做了优化实际上是a+b+c</span><br><span class="line">00050 (asm1.go:5)	MOVQ	AX, &quot;&quot;.d(SP)    &#x2F;&#x2F;结果放到局部变量d中</span><br><span class="line">00054 (asm1.go:6)	MOVQ	AX, &quot;&quot;.~r2+48(SP) &#x2F;&#x2F;设置返回值1 </span><br><span class="line">00059 (asm1.go:6)	MOVB	$1, &quot;&quot;.~r3+56(SP) &#x2F;&#x2F;设置返回值2为true</span><br><span class="line">00064 (asm1.go:6)	MOVQ	16(SP), BP &#x2F;&#x2F;还原原来的BP</span><br><span class="line">00069 (asm1.go:6)	ADDQ	$24, SP   &#x2F;&#x2F;调用结束缩小栈</span><br><span class="line">00073 (asm1.go:6)	RET</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">&quot;&quot;.main STEXT size&#x3D;138 args&#x3D;0x0 locals&#x3D;0x40</span><br><span class="line">00000 (asm1.go:9)	TEXT	&quot;&quot;.main(SB), $64-0</span><br><span class="line">00000 (asm1.go:9)	MOVQ	(TLS), CX  &#x2F;&#x2F;检查栈是否要扩容</span><br><span class="line">00009 (asm1.go:9)	CMPQ	SP, 16(CX) &#x2F;&#x2F;比较g-&gt;stackguard0</span><br><span class="line">00013 (asm1.go:9)	JLS	128 &#x2F;&#x2F;SP小于g-&gt;stackguard0就跳转到地址128</span><br><span class="line">00015 (asm1.go:9)	SUBQ	$64, SP &#x2F;&#x2F;对SP做减法，为函数分配函数栈帧</span><br><span class="line">00019 (asm1.go:9)	MOVQ	BP, 56(SP) &#x2F;&#x2F;将当前BP压栈暂存</span><br><span class="line">00024 (asm1.go:9)	LEAQ	56(SP), BP&#x2F;&#x2F;将当前sp的地址作为新的BP</span><br><span class="line"></span><br><span class="line">00029 (asm1.go:10)	MOVQ	$10, (SP) &#x2F;&#x2F;子调用的参数1</span><br><span class="line">00037 (asm1.go:10)	MOVQ	$32, 8(SP)&#x2F;&#x2F;子调用的参数2</span><br><span class="line">00046 (asm1.go:10)	CALL	&quot;&quot;.add(SB) &#x2F;&#x2F;调用函数</span><br><span class="line">00051 (asm1.go:10)	MOVQ	16(SP), AX &#x2F;&#x2F;获取返回值1 int</span><br><span class="line">00056 (asm1.go:10)	MOVQ	AX, &quot;&quot;..autotmp_2+48(SP)&#x2F;&#x2F;放置到临时变量2</span><br><span class="line"></span><br><span class="line">00061 (asm1.go:10)	MOVBLZX	24(SP), AX &#x2F;&#x2F;获取返回值2 bool</span><br><span class="line">00066 (asm1.go:10)	MOVB	AL, &quot;&quot;..autotmp_3+39(SP) &#x2F;&#x2F;放到临时变量3</span><br><span class="line"></span><br><span class="line">00070 (asm1.go:10)	MOVQ	&quot;&quot;..autotmp_2+48(SP), AX</span><br><span class="line">00075 (asm1.go:10)	MOVQ	AX, &quot;&quot;.e+40(SP)     &#x2F;&#x2F;返回值1赋值到局部变量e</span><br><span class="line"></span><br><span class="line">00080 (asm1.go:10)	MOVBLZX	&quot;&quot;..autotmp_3+39(SP), AX</span><br><span class="line">00085 (asm1.go:10)	MOVB	AL, &quot;&quot;.u+38(SP)    &#x2F;&#x2F;返回值2赋值到局部变量u</span><br><span class="line"></span><br><span class="line">00089 (asm1.go:11)	LEAQ	&quot;&quot;.u+38(SP), AX	    &#x2F;&#x2F;</span><br><span class="line">00094 (asm1.go:11)	CMPB	(AX), $0 &#x2F;&#x2F;比较u是否等于0</span><br><span class="line">00097 (asm1.go:11)	JNE	101    &#x2F;&#x2F;不为true（0）跳转到地址101</span><br><span class="line">00099 (asm1.go:11)	JMP	126   &#x2F;&#x2F;-&gt;126-&gt;116</span><br><span class="line">00101 (asm1.go:12)	MOVQ	&quot;&quot;.e+40(SP), AX      </span><br><span class="line">00106 (asm1.go:12)	INCQ	AX     </span><br><span class="line">00109 (asm1.go:12)	MOVQ	AX, &quot;&quot;.e+40(SP) &#x2F;&#x2F;对参数e+1</span><br><span class="line">00114 (asm1.go:12)	JMP	116</span><br><span class="line">00116 (&lt;unknown line number&gt;)	MOVQ	56(SP), BP &#x2F;&#x2F;还原BP</span><br><span class="line">00121 (&lt;unknown line number&gt;)	ADDQ	$64, SP	   &#x2F;&#x2F;调用结束缩小栈</span><br><span class="line">00125 (&lt;unknown line number&gt;)	RET</span><br><span class="line">00126 (asm1.go:11)	JMP	116</span><br><span class="line">00128 (asm1.go:11)	NOP &#x2F;&#x2F;调用函数前加nop，不是直接翻译成空语句，某些平台可能有用</span><br><span class="line">00128 (asm1.go:9)	CALL	runtime.morestack_noctxt(SB)</span><br><span class="line">00133 (asm1.go:9)	JMP	0</span><br></pre></td></tr></table></figure>

<p>拓展说明：</p>
<ol>
<li><p>$24-32 表示$framesize-argsize,表示栈帧大小为24个字节，参数返回值总和为32个字节</p>
</li>
<li><p>autotmp_3，autotmp_2是编译器自动生成的临时变量，因为编译器优化尽量只使用通用寄存器AX，所以需要临时变量来暂存结果</p>
</li>
</ol>
<h3 id="函数调用过程的栈帧变化"><a href="#函数调用过程的栈帧变化" class="headerlink" title="函数调用过程的栈帧变化"></a>函数调用过程的栈帧变化</h3><p><img src="http://river.marvelthanos.com/blog/20200302/EpgwnXzB4aFt.png?imageslim" alt="mark"></p>
<p>补充说明</p>
<ol>
<li>在整个调用过程中都是使用同一个栈，只是通过对SP寄存器的加减来实现栈的伸缩，同时偏移量也发生了对应的变化。</li>
<li>如果当前函数调用了其它函数，那么 return addr 也是在 caller 的栈上的，不过往栈上插 return addr 的过程是<strong>由 CALL 指令</strong>完成的</li>
<li>返回值通过压栈出栈来获取，所以可以返回多个参数，C语言只能通过AX寄存器来返回结果，所以只能返回一个参数</li>
<li>autotmp_3临时变量在39-40的位置只有一个字节，本质没什么用，为了避免混淆图中没标出来</li>
</ol>
<h3 id="栈帧伸缩过程"><a href="#栈帧伸缩过程" class="headerlink" title="栈帧伸缩过程"></a>栈帧伸缩过程</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">00000 (asm1.go:3)	SUBQ	$24, SP    &#x2F;&#x2F;对SP做减法，为函数分配函数栈帧</span><br><span class="line">00004 (asm1.go:3)	MOVQ	BP, 16(SP) &#x2F;&#x2F;将当前BP压栈暂存</span><br><span class="line">00009 (asm1.go:3)	LEAQ	16(SP), BP &#x2F;&#x2F;将当前sp的地址作为新的BP</span><br><span class="line">......</span><br><span class="line">00064 (asm1.go:6)	MOVQ	16(SP), BP &#x2F;&#x2F;还原原来的BP</span><br><span class="line">00069 (asm1.go:6)	ADDQ	$24, SP   &#x2F;&#x2F;调用结束缩小栈</span><br></pre></td></tr></table></figure>



<h2 id="golang栈扩容"><a href="#golang栈扩容" class="headerlink" title="golang栈扩容"></a>golang栈扩容</h2><p><a href="https://kirk91.github.io/posts/2d571d09/?spm=a2c4e.11153940.blogcont658456.13.59a86320nQnsTb" target="_blank" rel="noopener">聊一聊goroutine stack</a></p>
<p>我们先了解下协程栈的内存布局和一些重要的术语:</p>
<p><img src="http://river.marvelthanos.com/blog/20200302/AXecNrBfAei9.png?imageslim" alt="mark"></p>
<ul>
<li>stack.lo: 栈空间的低地址</li>
<li>stack.hi: 栈空间的高地址</li>
<li>stackguard0: stack.lo + StackGuard, 用于stack overlow的检测</li>
<li>StackGuard: 保护区大小，常量Linux上为880字节</li>
<li>StackSmall: 常量大小为128字节，用于小函数调用的优化</li>
<li>详细定义参考源码Go\src\runtime\stack.go</li>
</ul>
<p>stack.lo-stackgurd0是保留区，其中</p>
<ul>
<li><p>StackSmall用于小函数调用</p>
</li>
<li><p>StackGuard-StackSmall用于保存可能需要调用的函数的栈帧，比如有时候要放 morestack 的栈帧。</p>
</li>
</ul>
<p>一旦SP伸展到这个区域就说明可能需要扩容了。</p>
<p>在判断栈空间是否需要扩容的时候，可以根据被调用函数栈帧的大小, 分为以下两种情况:</p>
<ul>
<li>小于StackSmall（128字节）时，如果SP小于stackguard0，则执行栈扩容，否则直接执行函数</li>
<li>大于StackSamll时，计算SP - framesize + StackSmall 小于stackguard0, 执行栈扩容，否则直接执行。</li>
</ul>
<blockquote>
<p>runtime中还有个StackBig的常量，默认为4096，被调用函数栈帧大小大于StackBig的时候， 一定会发生栈的扩容，这里就不再展开了。</p>
</blockquote>
<h3 id="NOSPLIT的作用"><a href="#NOSPLIT的作用" class="headerlink" title="NOSPLIT的作用"></a>NOSPLIT的作用</h3><p>NOSPLIT的作用就是，无论栈帧是否大于StackSamll，都不插入检测代码，可以优化执行速度，但是会有栈溢出的风险，但是go的编译器已经足够聪明，只有确保不会栈溢出才会插入此标记。在手写汇编时加入此标记，而编译时编译器发现栈溢出就会报错，不能通过编译</p>
<h3 id="检测栈溢出和扩容的代码"><a href="#检测栈溢出和扩容的代码" class="headerlink" title="检测栈溢出和扩容的代码"></a>检测栈溢出和扩容的代码</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">00000 (asm1.go:9)	MOVQ	(TLS), CX  &#x2F;&#x2F;检查栈是否要扩容</span><br><span class="line">00009 (asm1.go:9)	CMPQ	SP, 16(CX) &#x2F;&#x2F;比较g-&gt;stackguard0</span><br><span class="line">00013 (asm1.go:9)	JLS	128 &#x2F;&#x2F;SP小于g-&gt;stackguard0就跳转到地址128</span><br><span class="line">...</span><br><span class="line">00128 (asm1.go:11)	NOP   &#x2F;&#x2F;调用函数前加个空指令，属于编译器优化</span><br><span class="line">00128 (asm1.go:9)	CALL	runtime.morestack_noctxt(SB)</span><br></pre></td></tr></table></figure>

<p>(TLS)取到的是结构体G的第一个域，也就是g-&gt;stackguard地址，将它赋值给CX。然后CX地址的值与SP进行比较，如果SP大于g-&gt;stackguard了，则会调用runtime.morestack函数。这几条指令的作用就是检测栈是否溢出。</p>
<p>有时候取TLS可能会遇到这样取TLS</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">MOVQ	TLS, CX</span><br><span class="line">MOVQ	(CX)(TLS*2), CX</span><br></pre></td></tr></table></figure>

<p>其实效果是一样, 为什么要这么做可能是为了作某种校验吧</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">MOVQ TLS, CX</span><br><span class="line">LEAQ (CX)(TLS*2), BX</span><br><span class="line">CX &#x3D; BX &#x2F;&#x2F;值是一样</span><br></pre></td></tr></table></figure>

<h3 id="栈帧大于StackSmall"><a href="#栈帧大于StackSmall" class="headerlink" title="栈帧大于StackSmall"></a>栈帧大于StackSmall</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">add2</span><span class="params">(x, y <span class="keyword">int</span>)</span> <span class="title">int</span></span> &#123;</span><br><span class="line">	_ = <span class="built_in">make</span>([]<span class="keyword">byte</span>, <span class="number">210</span>)</span><br><span class="line">	<span class="keyword">return</span> x + y</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&quot;&quot;.add2 STEXT size&#x3D;161 args&#x3D;0x18 locals&#x3D;0xe0</span><br><span class="line">	TEXT	&quot;&quot;.add2(SB), ABIInternal, $224-24</span><br><span class="line">	MOVQ	TLS, CX</span><br><span class="line">	MOVQ	(CX)(TLS*2), CX</span><br><span class="line">	LEAQ	-96(SP), AX </span><br><span class="line">	CMPQ	AX, 16(CX)</span><br><span class="line">	JLS	151</span><br><span class="line">......</span><br></pre></td></tr></table></figure>

<p>可以看到栈帧framesize=224</p>
<p>SP-224+128=&gt;SP-96=&gt;<code>LEAQ    -96(SP), AX</code></p>
<h3 id="为什么是CX-16"><a href="#为什么是CX-16" class="headerlink" title="为什么是CX(16)"></a>为什么是CX(16)</h3><p>因为stackguard0离结构体头，距离16个字节，在go汇编源码终会有很多通过首地址偏移指定字节数来定位不同属性的情况。</p>
<p>Go\src\runtime\runtime2.go</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> g <span class="keyword">struct</span> &#123;</span><br><span class="line">	stack       stack   <span class="comment">// 16 bytes 所以要跳过</span></span><br><span class="line">	<span class="comment">// stackguard0 is the stack pointer compared in the Go stack growth prologue.</span></span><br><span class="line">	<span class="comment">// It is stack.lo+StackGuard normally, but can be StackPreempt to trigger a preemption.</span></span><br><span class="line">	stackguard0 <span class="keyword">uintptr</span> <span class="comment">//stack.lo（栈低地址） + StackGuard, 用于stack overlow的检测</span></span><br><span class="line">	stackguard1 <span class="keyword">uintptr</span></span><br><span class="line"></span><br><span class="line">	<span class="comment">// ...omitted dozens of fields...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>







<h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><p><a href="https://github.com/cch123/golang-notes/blob/master/assembly.md" target="_blank" rel="noopener">plan9 assembly 完全解析</a>   </p>
<p><a href="https://lrita.github.io/2017/12/12/golang-asm/" target="_blank" rel="noopener">golang 汇编——源代码</a></p>
<p><a href="https://draveness.me/golang/docs/part2-foundation/ch04-basic/golang-function-call/" target="_blank" rel="noopener">draveness写的函数调用分析</a></p>
<p><a href="https://deepu.tech/memory-management-in-programming/" target="_blank" rel="noopener">Demystifying memory management in modern programming languages</a></p>

        
<script src="/blog/js/qrious.min.js"></script>


        <hr>
        <div>
            
            <p>
                <span>专题: </span>
                
                <a href="/blog/series/#Golang汇编">
                    <code key="Golang汇编" class="post-label">Golang汇编</code>
                </a>
                
            </p>
            
            <p>
                <span>本文发表于 2020-02-16，最后修改于 2020-03-13。</span>
            </p>
            <!-- 文章末尾的提示 start -->
            
            
            
                
            <!-- 文章末尾的提示 end -->
        </div>
        <hr />
        <p name="pagination" style="font-size: 1.2em">
    
    <a class="float-left" href="/blog/post/go-asm-indroduce.html">上一篇 « Golang汇编(一)-Plan9介绍及指令详解</a>
    
    
    <a class="float-right" href="/blog/post/go-asm-my-asm.html">下一篇 » Golang汇编(三)-编写自己的汇编代码</a>
    
</p>
    </article>
    <!-- 赞赏 -->
    <!--打赏-->

    <!-- 推荐阅读三篇文章 -->
    <div class="col-xs-12">
        <h3>推荐阅读</h3>
        
        <div class="post-preview">
    <div class="post-img" style="background-image: url('/blog/img/database/database.webp')"></div>
    <div class="post-info">
        <div class="post-info-center">
            <div class="hidden-xs">
                
                
                <span>/</span>
                
                <a class="text-gray" href="/blog/tags/#MySQL"
                    title="MySQL">MySQL</a>
                <span>/</span>
                
                <a class="text-gray" href="/blog/tags/#MySQL优化"
                    title="MySQL优化">MySQL优化</a>
                <span>/</span>
                
                
            </div>
            <a href="/blog/post/mysql-optimize-explain.html" class="title">
                EXPLAIN-执行计划查询命令
            </a>
            <p class="text-gray">
                <small>
                    <span>发表于2020-03-03，</span>
                    <span class="hidden-xs">全文11409字，</span>
                    <span>阅读约39分钟</span>
                </small>
            </p>
        </div>
    </div>
</div>
        
        <div class="post-preview">
    <div class="post-img" style="background-image: url('/blog/img/go.webp')"></div>
    <div class="post-info">
        <div class="post-info-center">
            <div class="hidden-xs">
                
                
                <span>/</span>
                
                <a class="text-gray" href="/blog/tags/#Golang底层"
                    title="Golang底层">Golang底层</a>
                <span>/</span>
                
                <a class="text-gray" href="/blog/tags/#Golang"
                    title="Golang">Golang</a>
                <span>/</span>
                
                
            </div>
            <a href="/blog/post/go-how-to-build.html" class="title">
                Golang程序编译执行流程
            </a>
            <p class="text-gray">
                <small>
                    <span>发表于2020-03-01，</span>
                    <span class="hidden-xs">全文7487字，</span>
                    <span>阅读约25分钟</span>
                </small>
            </p>
        </div>
    </div>
</div>
        
        <div class="post-preview">
    <div class="post-img" style="background-image: url('/blog/img/tcp/tcp.webp')"></div>
    <div class="post-info">
        <div class="post-info-center">
            <div class="hidden-xs">
                
                
                <span>/</span>
                
                <a class="text-gray" href="/blog/tags/#基础"
                    title="基础">基础</a>
                <span>/</span>
                
                <a class="text-gray" href="/blog/tags/#tcp/http"
                    title="tcp/http">tcp/http</a>
                <span>/</span>
                
                
            </div>
            <a href="/blog/post/tcp-three-way-handshake.html" class="title">
                简述TCP“3次握手，4次挥手”
            </a>
            <p class="text-gray">
                <small>
                    <span>发表于2020-02-21，</span>
                    <span class="hidden-xs">全文9978字，</span>
                    <span>阅读约34分钟</span>
                </small>
            </p>
        </div>
    </div>
</div>
        
    </div>
    <div class="col-xs-12">
        <!-- 标签列表 -->
        <!-- Featured Tags -->
<style>
    #featured-tag .post-tag-item {
        font-size: 12px;
        line-height: 30px;
        display: inline-block;
        height: 30px;
        margin: 5px 0px;
        padding: 0 7px;
        color: #333;
        border-radius: 15px;
        background: #f6f6f6;
    }

    #featured-tag .post-tag-item:hover {
        color: #337ab7;
    }
</style>
<div id="featured-tag">
    
    <a class="post-tag-item" href="/blog/tags/#关于我" title="关于我"
        rel="1">#关于我 (1) </a>
    
    <a class="post-tag-item" href="/blog/tags/#友链" title="友链"
        rel="1">#友链 (1) </a>
    
    <a class="post-tag-item" href="/blog/tags/#Golang汇编" title="Golang汇编"
        rel="4">#Golang汇编 (4) </a>
    
    <a class="post-tag-item" href="/blog/tags/#Golang底层" title="Golang底层"
        rel="5">#Golang底层 (5) </a>
    
    <a class="post-tag-item" href="/blog/tags/#Golang" title="Golang"
        rel="8">#Golang (8) </a>
    
    <a class="post-tag-item" href="/blog/tags/#Golang基础" title="Golang基础"
        rel="1">#Golang基础 (1) </a>
    
    <a class="post-tag-item" href="/blog/tags/#Golang源码" title="Golang源码"
        rel="2">#Golang源码 (2) </a>
    
    <a class="post-tag-item" href="/blog/tags/#基础" title="基础"
        rel="2">#基础 (2) </a>
    
    <a class="post-tag-item" href="/blog/tags/#tcp/http" title="tcp/http"
        rel="2">#tcp/http (2) </a>
    
    <a class="post-tag-item" href="/blog/tags/#MySQL" title="MySQL"
        rel="3">#MySQL (3) </a>
    
    <a class="post-tag-item" href="/blog/tags/#MySQL优化" title="MySQL优化"
        rel="1">#MySQL优化 (1) </a>
    
</div>
    </div>
    <!-- 评论 -->
    <div class="col-xs-12">
        
    </div>
</div>
<aside class="float-left gkt-sidebar hidden-xs hidden-sm">
    <div style="clear: both"></div>
    <div class="gkt-sidebar-wrapper">
        <section class="box-shadow"><style>
    .gkt-summary {
        border: 1px solid #DDDDDD;
        border-radius: 3px;
        padding: 5px;
        width: 100%;
    }


    .gkt-summary nav {
        overflow: hidden;
    }

    .gkt-summary nav a {
        display: inline-block;
        text-align: center;
        color: #333;
        font-size: 12px;
    }

    .gkt-summary nav span {
        display: block;
    }

    .gkt-summary nav .middle {
        border-left: 1px solid #eaecef;
        border-right: 1px solid #eaecef;
    }

    .gkt-summary .number {
        font-weight: bold;
    }

    .gkt-summary .link-list {
        margin-top: 5px;
        margin-bottom: -5px;
        padding-top: 7px;
        border-top: 1px dashed #999;
        display: flex;
    }

    .gkt-summary .link-list a {
        flex: 1;
    }

    .gkt-summary .link-list img {
        width: 25px;
        height: 25px;
    }
</style>

<div class="gkt-summary">
    <nav>
        <a href="/blog/" class="col-xs-4">
            <number class="number">15</number><span>文章</span>
        </a>
        <a href="/blog/series" class="col-xs-4 middle">
            <number class="number">5</number><span>专题</span>
        </a>
        <a href="/blog/tags" class="col-xs-4">
            <number class="number">11</number><span>标签</span>
        </a>
    </nav>

    
    <div class="link-list">
        
        <a href="https://marvelthanos.com" target="_blank" rel="noopener"><img src="/blog/img/related_links/thanos.webp" /></a>
        
    </div>
    
</div></section>
        
        
        <section class="gkt-sidebar-content box-shadow">
            <strong>Golang汇编</strong>
            <ul>
                
                <li>
                    <a href="/blog/post/go-asm-indroduce.html"
                        class="">Golang汇编(一)-Plan9介绍及指令详解</a>
                    
                </li>
                
                <li>
                    <a href="/blog/post/go-asm-method.html"
                        class="gkt-sidebar-active">Golang汇编(二)-函数调用</a>
                    
                    <!-- Table of Contents -->
<div id="sidebar-toc">
  <!-- TOC  -->
  
  <ol class="toc-nav"><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#函数调用栈"><span class="toc-nav-text">函数调用栈</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#简单案例"><span class="toc-nav-text">简单案例</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#golang栈扩容"><span class="toc-nav-text">golang栈扩容</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#参考资料"><span class="toc-nav-text">参考资料</span></a></li></ol>
  
</div>

<script>
  (function () {
    var h2 = document.querySelectorAll('article h2');
    var h3 = document.querySelectorAll('article h3');
    var linkList = document.querySelectorAll('#sidebar-toc a');

    function findLinkElement(name) {
      for (var i = 0; i < linkList.length; i++) {
        var items = linkList[i].href.split('#');
        if (items && items[items.length - 1] === encodeURIComponent(name)) {
          return i;
        }
      }
      return -1;
    }

    function activeLink(titleList) {
      var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
      for (var i = titleList.length - 1; i >= 0; i--) {
        if (scrollTop - titleList[i].offsetTop > 0) {
          var index = findLinkElement(titleList[i].id);
          index != -1 && linkList[index].classList.add('gkt-sidebar-active');
          break;
        }
      }
    }

    window.addEventListener("scroll", function (e) {
      [].slice.call(linkList).forEach(function (link) {
        link.classList.remove('gkt-sidebar-active');
      })
      activeLink(h2);
    })
  })();
</script>
                    
                </li>
                
                <li>
                    <a href="/blog/post/go-asm-my-asm.html"
                        class="">Golang汇编(三)-编写自己的汇编代码</a>
                    
                </li>
                
                <li>
                    <a href="/blog/post/go-asm-datastruct.html"
                        class="">Golang汇编(四)-汇编眼中的数据结构</a>
                    
                </li>
                
            </ul>
        </section>
        
        
        <section class="box-shadow">
            <strong>最近的文章</strong>
            <ul>
                
                <li>
                    <a href="/blog/post/go-learn-lib-sorts.html">Go源码学习 sort包</a>
                </li>
                
                <li>
                    <a href="/blog/post/mysql-optimize-explain.html">EXPLAIN-执行计划查询命令</a>
                </li>
                
                <li>
                    <a href="/blog/post/mysql-index-base.html">《高性能MySQL》笔记-索引基础</a>
                </li>
                
            </ul>
        </section>
        <section class="box-shadow text-center">
            
        </section>
    </div>
</aside>

<script>
    (function () {
        var ele = getDom('.gkt-sidebar-content')
        var wrapper = getDom('.gkt-sidebar-wrapper')
        var last = 0
        window.addEventListener("scroll", function (e) {
            var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
            var isDown = scrollTop > last;
            var pos = getPosition(ele).y - scrollTop;
            var downLimit = 50;
            var upLimit = -100;
            // uarrow.style.marginTop = scrollTop + 'px';
            isDown && pos <= downLimit && wrapper.classList.add("gkt-sidebar-fixed");
            !isDown && pos > upLimit && wrapper.classList.remove("gkt-sidebar-fixed");
            last = scrollTop
        })
    })();
    (function () {
        function resizeUArrow() {
            var s = getDom('.u-arrow-wrapper').style
            var pc = getDom('.post-container')
            s.left = getPosition(pc).x + 'px';
            s.width = pc.clientWidth + 'px';
        }
        resizeUArrow()
        window.addEventListener('resize', resizeUArrow);
    })();
</script>

    
</main>
    </div>
    <style>
    img#go-top {
        position: fixed;
        bottom: 100px;
        width: 50px;
        cursor: pointer;
        z-index: 9999;
    }
</style>
<img id="go-top" src="/blog/icon/top.png" class="hidden-xs" style="display: none" />
<script>
    (function () {
        var goTop = document.getElementById('go-top');
        var mainContainer = document.querySelector('.main-container');
        
        goTop.addEventListener('click', function () {
            window.scroll(0, 0);
        }, false);
        window.addEventListener('scroll', function () {
            var right = document.body.offsetWidth - mainContainer.getBoundingClientRect().right;
            var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
            goTop.style.right = right + 10 + 'px'
            scrollTop > 700 && (goTop.style.display = "block");
            scrollTop <= 700 && (goTop.style.display = "none");
        });
    })();
</script>
    <style>
    #geektutu-click-img-container {
        position: fixed;
        left: 0;
        top: 0;
        text-align: center;
        width: 100%;
        display: none;
        z-index: 9999;
    }

    #geektutu-click-img-container img {
        object-fit: contain;
        background: #eaecef;
        padding: 15px;
        border-radius: 10px;
        height: auto;
        width: auto;
        vertical-align: middle;
    }
</style>


<div id="geektutu-click-img-container">
    <img src="" alt="Big Image">
</div>

<script>
    (function () {
        var container = document.querySelector('#geektutu-click-img-container')
        var targetImg = container.querySelector('img')
        var imgs = document.querySelectorAll('article img');
        targetImg.addEventListener('click', function (e) {
            container.style.display = 'none';
            e && e.stopPropagation && e.stopPropagation();
        }, false);

        for (var i = 0; i < imgs.length; ++i) {
            var img = imgs[i];
            img.addEventListener('click', (function (src, rate) {
                return function (e) {
                    e && e.stopPropagation && e.stopPropagation();
                    if (window.innerWidth < 980) {
                        return
                    }
                    targetImg.style.height = targetImg.style.width = 'auto';
                    if (window.innerWidth / window.innerHeight > rate) {
                        targetImg.style.height = (window.innerHeight - 20) + 'px';
                    } else {
                        targetImg.style.width = (window.innerWidth - 20) + 'px';
                    }
                    container.style.height = window.innerHeight + 'px'
                    container.style.lineHeight = window.innerHeight + 'px'
                    container.style.display = 'block';
                    targetImg.src = src;
                };
            }(img.src, img.width / img.height)), false)
        }
    })();
</script>
    <!-- Footer -->
    <!-- Footer -->
<style>
    footer {
        width: 100%;
        line-height: 1.5;
        padding: 20px;
    }

    footer a {
        color: #333;
        text-decoration: none;
    }

    .footer-hexo img {
        height: 20px;
        margin-bottom: -5px;
    }

    .footer-hexo a {
        color: #337ab7;
    }
</style>
<footer class="text-center col-xs-12">
    <p>
        <small>© 2020 - Evrn - </small>
        <small>
            <a target="_blank" rel="nofollow noopener" href="http://www.miitbeian.gov.cn/"></a>
        </small>
    </p>
    <p class="footer-hexo">
        <!-- 但若直接使用或修改主题，请务必保留这段声明 -->
        <small>Powered by <a target="_blank" href="https://hexo.io">Hexo</a> | Theme
            <a target="_blank" href="https://geektutu.com">Geektutu</a>
            <a href="https://github.com/geektutu/hexo-theme-geektutu" target="_blank" rel="noopener">
                <img src=""
                    alt="Github Star">
            </a>
        </small>
    </p>
    
    <p>
        <small>
            <span id="busuanzi_container_site_pv">👁<span id="busuanzi_value_site_pv"></span></span> &nbsp;
            <span id="busuanzi_container_page_pv">📚<span id="busuanzi_value_page_pv"></span></span>
        </small>
    </p>
    
    

</footer>


<script>
    window.addEventListener('load', function () {
        globalAddScript('//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js');
    })
</script>

<!-- Baidu Tongji -->





    <script>
        // 非本站网址，新开tab页签
        (function () {
            var stopBubble = function (e) {
                e && e.stopPropagation && e.stopPropagation();
            }
            var links = document.querySelectorAll('a');
            [].slice.call(links).forEach(function (item) {
                if (item.href && item.href.indexOf(window.location.host) === -1) {
                    item.target = '_blank'
                }
                // 阻止冒泡，不触发彩蛋。
                item.addEventListener('click', stopBubble, false);
            });
            var article = document.querySelector('article');
            article && article.addEventListener('click', stopBubble, false)
        })();
    </script>
    
</body>

</html>